package com.baitiaojun.moments.support;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baitiaojun.api.domain.user.UserInfo;
import com.baitiaojun.api.exception.ConditionException;
import com.baitiaojun.moments.feign.UserFeign;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.redisson.api.RBloomFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * @类名 RedisSuport
 * @作者 白条君
 * @创建日期 2022/10/28 15:15
 * @版本 1.0
 */
@Slf4j
@Getter
@Component
public class RedisSupport {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

//    @Autowired
//    private RedissonConfig redisson;

    @Autowired
    private UserFeign userFeign;

    private static final Lock LOCK = new ReentrantLock();

    public ValueOperations<String, Object> string() {
        return redisTemplate.opsForValue();
    }

    public HashOperations<String, String, Object> hash() {
        return redisTemplate.opsForHash();
    }

    public ZSetOperations<String, Object> zSet() {
        return redisTemplate.opsForZSet();
    }

    public void setHash(String key, String hashKey, Object value) {
        hash().put(key, hashKey, value);
    }

    public Object getHash(String key, String hashKey) {
        return hash().get(key, hashKey);
    }

    public void deleteHash(String key, String hashKey) {
        Map<String, Object> entries = hash().entries(key);
        for (Map.Entry<String, Object> entry : entries.entrySet()) {
            if (entry.getKey().equals(hashKey)) {
                hash().delete(key, hashKey);
                break;
            }
        }
    }

    public void deleteZSet(String key, String hashKey) {
        zSet().remove(key + ":page", hashKey);
    }

    @Async
    public void asyncDelete(Long userId) throws Exception {
        Object data = userFeign.getFansList(userId).get("data");
        if (ObjectUtils.isNotEmpty(data)) {
            Set<Long> fansUserIds = JSONArray.parseArray(JSONObject.toJSONString(data), UserInfo.class).stream().map(UserInfo::getUserid).collect(Collectors.toSet());
            for (Long fansUserId : fansUserIds) {
                String fansKey = "subscribed" + fansUserId;
                this.delete(fansKey + ":page");
                this.delete(fansKey);
            }
        }
    }

    public long hashSize(String key) {
        return hash().size(key);
    }

    public void setPage(String key, String hashKey, Object value) {
        try {
            while (true) {
                if (LOCK.tryLock(15000, TimeUnit.SECONDS)) {
                    double score = this.hashSize(key) + 1;
                    zSet().add(key + ":page", hashKey, score);
                    this.setHash(key, hashKey, value);
                    break;
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            LOCK.unlock();
        }
    }

    public List<Object> getPage(String key, int pageNo, int pageSize) {
        Set<String> ids = new HashSet<>();
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = zSet().reverseRangeWithScores(key + ":page", 0, 0);
        if (ObjectUtils.isNotEmpty(typedTuples)) {
            Double maxScore = typedTuples.iterator().next().getScore();
            if (ObjectUtils.isNotEmpty(maxScore)) {
                Set<Object> objects = zSet().rangeByScore(key + ":page", 0, maxScore, (long) (pageNo - 1) * pageSize, pageSize);
                if (objects != null) {
                    ids = Objects.requireNonNull(objects.stream()
                            .map(String::valueOf)
                            .sorted(Comparator.reverseOrder())
                            .collect(Collectors.toCollection(LinkedHashSet::new)));
                }
            }
        }
        return hash().multiGet(key, ids);
    }

    public <T> Boolean setNx(String key, T value, Long expire, TimeUnit timeUnit) throws ConditionException {
        Boolean result;
        try {
            result = redisTemplate.execute((RedisCallback<Boolean>) connection ->
                    connection.set(key.getBytes(StandardCharsets.UTF_8)
                            , String.valueOf(value).getBytes(StandardCharsets.UTF_8)
                            , Expiration.from(expire, timeUnit)
                            , RedisStringCommands.SetOption.ifAbsent()));
        } catch (Exception e) {
            throw new ConditionException("缓存设置异常");
        }
        return Boolean.TRUE.equals(result);
    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }

    //弃用
    @Deprecated
    public void syncRedis(Long userId, Object userMoments, Long contentId) {
//        RBloomFilter<Object> bloomFilter = redisson.bloomFilter();
        String key = "subscribed" + userId;
        this.setPage(key, String.valueOf(contentId), userMoments);
//        bloomFilter.add(key);
    }
}